import numpy as np
import pandas as pd
from sklearn.metrics.pairwise import cosine_similarity
from sklearn.preprocessing import MinMaxScaler, MultiLabelBinarizer
from collections import defaultdict

# 加载数据
df = pd.read_csv("cleaned_tourism_data.csv")
df['Sites Visited'] = df['Sites Visited'].str.split(', ')  # 转换为列表

# 处理综合评分
df['Tourist Rating'] = pd.to_numeric(df['Tourist Rating'], errors='coerce').fillna(df['Tourist Rating'].mean())
df['Satisfaction'] = pd.to_numeric(df['Satisfaction'], errors='coerce').fillna(df['Satisfaction'].mean())
df['Normalized_Rating'] = (df['Tourist Rating'] - df['Tourist Rating'].min()) / (
            df['Tourist Rating'].max() - df['Tourist Rating'].min())
df['Normalized_Satisfaction'] = (df['Satisfaction'] - df['Satisfaction'].min()) / (
            df['Satisfaction'].max() - df['Satisfaction'].min())
df['Composite_Score'] = (0.5 * df['Normalized_Rating'] + 0.5 * df['Normalized_Satisfaction']) * 5

# 创建用户-景点评分矩阵
exploded_df = df.explode('Sites Visited')
ratings_df = exploded_df[['Tourist ID', 'Sites Visited', 'Composite_Score']]
user_counts = ratings_df['Tourist ID'].value_counts()
valid_users = user_counts[user_counts >= 2].index
filtered_ratings = ratings_df[ratings_df['Tourist ID'].isin(valid_users)]
user_item_matrix = filtered_ratings.pivot_table(
    index='Tourist ID',
    columns='Sites Visited',
    values='Composite_Score',
    aggfunc='mean',
    fill_value=0
)

# 提取用户特征
user_features = df.groupby('Tourist ID').agg({
    'Age': 'first',
    'Interests': 'first',
    'Preferred Tour Duration': 'first'
}).reset_index()

# 处理Interests的多热编码
user_features['Interests'] = user_features['Interests'].str.split(', ')
mlb = MultiLabelBinarizer()
interests_encoded = pd.DataFrame(
    mlb.fit_transform(user_features['Interests']),
    columns=mlb.classes_,
    index=user_features.index
)
user_features = pd.concat([user_features.drop(['Interests'], axis=1), interests_encoded], axis=1)

# 归一化Age和Duration
scaler = MinMaxScaler()
user_features[['Age_Norm', 'Duration_Norm']] = scaler.fit_transform(
    user_features[['Age', 'Preferred Tour Duration']]
)
user_features.drop(['Age', 'Preferred Tour Duration'], axis=1, inplace=True)

# 创建用户-景点访问的二进制矩阵
user_sites = exploded_df.groupby(['Tourist ID', 'Sites Visited']).size().unstack(fill_value=0).clip(upper=1)
user_features = user_features.merge(
    user_sites,
    left_on='Tourist ID',
    right_index=True,
    how='left'
).fillna(0)

# 设置索引
user_features.set_index('Tourist ID', inplace=True)

# 保存所有景点列名
site_columns = user_sites.columns.tolist()

# 预处理兴趣-景点评分数据（新增部分）
# =================================================================
# 合并用户兴趣和访问记录
user_interests = user_features[mlb.classes_].stack().reset_index()
user_interests = user_interests[user_interests[0] == 1]
user_interests = user_interests[['Tourist ID', 'level_1']].rename(columns={'level_1': 'Interest'})

# 合并到exploded_df中
exploded_with_interests = exploded_df.merge(user_interests, on='Tourist ID', how='left')

# 计算每个兴趣下每个景点的平均评分
interest_site_scores = exploded_with_interests.groupby(['Interest', 'Sites Visited'])[
    'Composite_Score'].mean().reset_index()

# 为每个兴趣生成景点排名
interest_site_rankings = interest_site_scores.groupby('Interest').apply(
    lambda x: x.sort_values('Composite_Score', ascending=False)['Sites Visited'].tolist()
).to_dict()

# 计算全局热门景点（按平均评分和访问次数）
global_site_popularity = exploded_df.groupby('Sites Visited').agg(
    avg_score=('Composite_Score', 'mean'),
    visit_count=('Composite_Score', 'count')
)
global_site_popularity['popularity'] = (global_site_popularity['avg_score'] * 0.7 +
                                        global_site_popularity['visit_count'] * 0.3)
global_site_popularity = global_site_popularity['popularity'].sort_values(ascending=False)

# 准备新用户处理函数
def prepare_new_user(new_user_info, mlb, scaler, site_columns):
    """处理新用户特征并转换为模型需要的格式"""
    # 处理兴趣特征
    interests = new_user_info.get('Interests', '')
    if isinstance(interests, str):
        interests = interests.split(', ') if interests else []

    # 多热编码
    interests_encoded = mlb.transform([interests])
    interests_df = pd.DataFrame(interests_encoded, columns=mlb.classes_)

    # 归一化数值特征
    age = new_user_info.get('Age', 30)  # 默认值
    duration = new_user_info.get('Preferred Tour Duration', 5)  # 默认值
    age_duration = scaler.transform([[age, duration]])

    # 构建特征字典
    user_data = {
        'Age_Norm': age_duration[0][0],
        'Duration_Norm': age_duration[0][1]
    }

    # 添加兴趣特征
    for interest in interests_df.columns:
        user_data[interest] = interests_df[interest].iloc[0]

    # 添加访问历史特征
    visited_sites = new_user_info.get('Sites Visited', [])
    for site in site_columns:
        user_data[site] = 1 if site in visited_sites else 0

    # 创建DataFrame并确保列对齐
    return pd.DataFrame([user_data]).reindex(columns=user_features.columns, fill_value=0)

# =================================================================

# 修改后的推荐函数
def get_recommendations_for_new_user(new_user_info, n=5, k=3):
    """为新用户生成景点推荐（混合推荐系统）"""
    # 准备新用户特征
    new_user_df = prepare_new_user(new_user_info, mlb, scaler, site_columns)

    # 计算相似度
    combined_users = pd.concat([user_features, new_user_df])
    similarity_matrix = cosine_similarity(combined_users)
    new_user_similarities = pd.Series(
        similarity_matrix[-1, :-1],  # 排除最后一行（新用户自己）
        index=user_features.index
    )

    # 获取有效相似用户
    possible_similar_users = new_user_similarities.nlargest(k * 2)
    valid_similar_users = possible_similar_users[
        possible_similar_users.index.isin(user_item_matrix.index)
    ].nlargest(k)

    recommendations = []
    visited = set(new_user_info.get('Sites Visited', []))

    # 协同过滤推荐（如果存在有效用户）
    if len(valid_similar_users) > 0:
        for site in user_item_matrix.columns:
            if site not in visited:
                try:
                    relevant_ratings = user_item_matrix.loc[valid_similar_users.index, site]
                    weighted_sum = (valid_similar_users.values * relevant_ratings).sum()
                    sum_weights = valid_similar_users.sum()
                    pred_score = weighted_sum / sum_weights if sum_weights != 0 else 0
                    recommendations.append((site, pred_score))
                except KeyError:
                    continue
        # 按预测评分排序
        recommendations = sorted(recommendations, key=lambda x: x[1], reverse=True)[:n]

    # 如果推荐不足，添加基于兴趣和热门的推荐
    if len(recommendations) < n:
        # 基于兴趣的推荐
        interests = new_user_info.get('Interests', [])
        if isinstance(interests, str):
            interests = interests.split(', ') if interests else []

        interest_recs = []
        for interest in interests:
            if interest in interest_site_rankings:
                interest_recs.extend(interest_site_rankings[interest])

        # 去重并排除已访问
        interest_recs = [site for site in interest_recs
                         if site not in visited and site in global_site_popularity]

        # 按出现频率和全局热度排序
        rec_counts = defaultdict(int)
        for site in interest_recs:
            rec_counts[site] += 1

        # 排序逻辑：先出现次数，再全局热度
        sorted_sites = sorted(rec_counts.keys(),
                              key=lambda x: (-rec_counts[x], -global_site_popularity[x]),
                              reverse=False)

        # 转换为推荐格式
        interest_based = [(site, global_site_popularity[site]) for site in sorted_sites]

        # 添加全局热门推荐
        global_recs = [(site, global_site_popularity[site])
                       for site in global_site_popularity.index
                       if site not in visited and site not in dict(recommendations)]

        # 合并推荐结果
        combined = recommendations + interest_based + global_recs

        # 去重并保留顺序
        seen = set()
        final_recs = []
        for site, score in combined:
            if site not in seen:
                seen.add(site)
                final_recs.append((site, score))

        # 确保推荐数量
        recommendations = final_recs[:n]

    return recommendations


# 示例使用
if __name__ == "__main__":
    new_user = {
        'Age': 30,
        'Interests': 'history, culture',
        'Preferred Tour Duration': 5,
        'Sites Visited': []
    }
    # 'Machu Picchu', 'Taj Mahal'
    recommendations = get_recommendations_for_new_user(new_user)
    print("Top 5 Recommended Sites:")
    for i, (site, score) in enumerate(recommendations, 1):
        print(f"{i}. {site} (Recommendation Score: {score:.2f})")